﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PublishedContentExtensions.cs" company="CodePlex">
//   The MIT License
//   Copyright (c) 2013 Julien Blin
//   
//   Permission is hereby granted, free of charge, to any person obtaining a copy
//   of this software and associated documentation files (the "Software"), to deal
//   in the Software without restriction, including without limitation the rights
//   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//   copies of the Software, and to permit persons to whom the Software is
//   furnished to do so, subject to the following conditions:
//   
//   The above copyright notice and this permission notice shall be included in
//   all copies or substantial portions of the Software.
//   
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//   THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace UCodeFirst
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using UCodeFirst.ContentTypes;
    using UCodeFirst.Converters;

    using Umbraco.Core;
    using Umbraco.Core.Models;
    using Umbraco.Web;

    using ContentTypeBase = UCodeFirst.ContentTypes.ContentTypeBase;

    /// <summary>
    /// Extensions method for <see cref="IPublishedContent"/>
    /// </summary>
    public static class PublishedContentExtensions
    {
        /// <summary>
        /// List of loaded content types types by alias.
        /// Bug: Forces the search based on string instead of the type itself.
        /// </summary>
        private static readonly Lazy<IDictionary<string, Type>> ContentTypeTypesByAlias = new Lazy<IDictionary<string, Type>>(
            () => TypesScanner.TypesInBin.Value.Where(x => x.GetAllBaseTypes().Select(y => y.FullName).InvariantContains("UCodeFirst.ContentTypes.ContentTypeBase")).ToDictionary(x => x.Name), true);

        /// <summary>
        /// Returns a strongly-typed <typeparamref name="T"/> content type.
        /// </summary>
        /// <param name="publishedContent">
        /// The published content.
        /// </param>
        /// <typeparam name="T">
        /// The type of content type.
        /// </typeparam>
        /// <returns>
        /// The instantiated content type.
        /// </returns>
        public static T ToStrongContent<T>(this IPublishedContent publishedContent)
            where T : ContentTypeBase
        {
            var content = ToStrongContent(publishedContent);

            if (content == null)
            {
                return null;
            }

            if (content is T)
            {
                return (T)content;
            }

            throw new UCodeFirstException(Resources.UnableToCastObject.InvariantFormat(content.GetType(), typeof(T)));
        }

        /// <summary>
        /// Returns a strongly-typed <see cref="ContentTypes.ContentTypeBase"/> content type.
        /// </summary>
        /// <param name="publishedContent">
        /// The published content.
        /// </param>
        /// <returns>
        /// The instantiated content type.
        /// </returns>
        public static ContentTypeBase ToStrongContent(this IPublishedContent publishedContent)
        {
            if (publishedContent == null || publishedContent.DocumentTypeAlias == null || publishedContent.Id == 0)
            {
                return null;
            }

            if (!ContentTypeTypesByAlias.Value.ContainsKey(publishedContent.DocumentTypeAlias))
            {
                throw new UCodeFirstException(Resources.UnableToFindContentType.InvariantFormat(publishedContent.DocumentTypeAlias));
            }

            var umbracoHelper = new UmbracoHelper(UmbracoContext.Current);
            var contentTypeType = ContentTypeTypesByAlias.Value[publishedContent.DocumentTypeAlias];
            var content = contentTypeType.CreateInstance<ContentTypeBase>();
            content.PublishedContent = publishedContent;

            MapBaseProperties(publishedContent, content);

            foreach (var publishedContentProperty in publishedContent.Properties)
            {
                var targetPropertyInfo = contentTypeType.GetProperty(publishedContentProperty.Alias);
                if (targetPropertyInfo == null)
                {
                    // property alias might have been converted to camel case by Umbraco.
                    targetPropertyInfo = contentTypeType.GetProperty(publishedContentProperty.Alias.FirstCharacterToUpper());
                    if (targetPropertyInfo == null)
                    {
                        continue;
                    }
                }

                var targetPropertyAttr = targetPropertyInfo.GetAttr<PropertyAttribute>();
                if (targetPropertyAttr == null)
                {
                    continue;
                }

                var converter = targetPropertyAttr.Converter == null
                                    ? StandardConverters.Converters.ContainsKey(targetPropertyInfo.PropertyType) ? StandardConverters.Converters[targetPropertyInfo.PropertyType] : null
                                    : targetPropertyAttr.Converter.CreateInstance<IConverter>();

                if (converter == null)
                {
                    throw new UCodeFirstException(Resources.UnableToFindConverter.InvariantFormat(publishedContentProperty.Alias));
                }

                var setMethod = targetPropertyInfo.GetSetMethod(true);
                if (setMethod == null)
                {
                    throw new UCodeFirstException(Resources.UnableToFindSetMethod.InvariantFormat(publishedContentProperty.Alias));
                }

                var renderedValue = publishedContentProperty.Value;
                try
                {
                    renderedValue = umbracoHelper.Field(content.PublishedContent, publishedContentProperty.Alias);
                }
                // ReSharper disable EmptyGeneralCatchClause
                catch
                // ReSharper restore EmptyGeneralCatchClause
                {
                    // In certain circumstances the previous call can fail, such as when rendering a macro in the back-end.
                }

                setMethod.Invoke(content, new[] { converter.Read(targetPropertyInfo, renderedValue.ToString()) });
            }

            return content;
        }

        /// <summary>
        /// Map common properties between <see cref="IPublishedContent"/> and <see cref="ContentTypes.ContentTypeBase"/>.
        /// </summary>
        /// <param name="publishedContent">
        /// The published content.
        /// </param>
        /// <param name="content">
        /// The content.
        /// </param>
        private static void MapBaseProperties(IPublishedContent publishedContent, ContentTypeBase content)
        {
            content.Id = publishedContent.Id;
            content.TemplateId = publishedContent.TemplateId;
            content.SortOrder = publishedContent.SortOrder;
            content.Name = publishedContent.Name;
            content.UrlName = publishedContent.UrlName;
            content.DocumentTypeAlias = publishedContent.DocumentTypeAlias;
            content.DocumentTypeId = publishedContent.DocumentTypeId;
            content.WriterName = publishedContent.WriterName;
            content.CreatorName = publishedContent.CreatorName;
            content.WriterId = publishedContent.WriterId;
            content.CreatorId = publishedContent.CreatorId;
            content.Path = publishedContent.Path;
            content.CreateDate = publishedContent.CreateDate;
            content.UpdateDate = publishedContent.UpdateDate;
            content.Version = publishedContent.Version;
            content.Level = publishedContent.Level;
            content.Url = publishedContent.Url;
        }
    }
}
